import socket
import sys
import threading
import time
import random

#############################################################
#变量定义
snake_limit = 2  #默认最大玩家数量，可以通过-max=n改变为n

NO_KEY = 0
UP = 1  #游戏方向键，1234代表上下左右，0为空格
DOWN = 2
LEFT = 3
RIGHT = 4
SPACE = 5

#规定屏幕尺寸
box_size = 20  #格子大小
max_x_box = 45  #格子数量，x轴
max_y_box = round(max_x_box * 9 / 16)  #格子数量，y轴

screen_width = box_size * max_x_box  #屏幕宽度
screen_height = box_size * max_y_box  #屏幕高度

x_limit = screen_width - box_size  #最大x坐标
y_limit = screen_height - box_size  #最大y坐标

#服务器参数
server_ip = ""  #服务器ipv4地址,为空则系统自动设为本机地址
server_port = 5555  #服务器端口号

#############################################################
#类的定义


class Snake():
    def __init__(self):
        self.id = 0
        self.xy = (0, 0)  #蛇头坐标
        self.old_direction = 0  #表示头方向，0为无方向，停止状态。上下左右分别为1234
        self.next_direction = 0  #将要移动的方向，为了避免两次90度修改方向叠加为180度，只在移动前的一刻才会把self.direction改成这个。
        self.color = (0, 0, 0)
        self.len = 0  #蛇长
        self.body = []  #蛇身，是个类似(1,2)坐标元组的list
        self.alive = True

    #如果不是180度转弯就更新方向，否则不做改动
    def change_direction(self, new_direction):
        if new_direction == self.old_direction: return

        if new_direction == UP and self.old_direction == DOWN: return
        if new_direction == DOWN and self.old_direction == UP: return
        if new_direction == LEFT and self.old_direction == RIGHT: return
        if new_direction == RIGHT and self.old_direction == LEFT: return

        self.next_direction = new_direction

    #判断蛇头是否与其它蛇冲突
    def hit_other_snakes(self):
        for id in range(snake_limit):
            if self.id == id:
                continue
            else:
                for body_xy in snakes[id].body:
                    if body_xy == self.xy:
                        return True
        return False

    #判断xy坐标是否与墙壁相撞
    def hit_walls(self):
        if self.xy[0] < 0 or self.xy[0] > x_limit or self.xy[1] < 0 or self.xy[
                1] > y_limit:
            return True
        else:
            return False

    #移动
    def move(self):
        self.old_direction = self.next_direction
        x = self.xy[0]
        y = self.xy[1]
        if self.old_direction == 0:  #0是特殊情况，初始时若客户端不按一个方向键，蛇就不开始移动
            return
        if self.old_direction == UP:
            y -= box_size
        elif self.old_direction == DOWN:
            y += box_size
        elif self.old_direction == LEFT:
            x -= box_size
        elif self.old_direction == RIGHT:
            x += box_size
        self.xy = (x, y)

        if self.hit_walls() or self.hit_other_snakes():
            self.alive = False
            self.clear()
            return
        for i in range(0, self.len - 2):
            if self.xy == self.body[i]:
                self.alive = False

        if colliding_with_food(self.xy):
            self.len += 1
            self.body.append(self.xy)
            food.remove(self.xy)
            new_food = find_empty_xy()
            food.add(new_food)

        self.body.append(self.xy)
        del self.body[0]

    #清除此蛇，不妨碍其他玩家
    def clear(self):
        self.xy = (-1, -1)
        self.body = [(-20, -20)]


class Food():
    def __init__(self):
        self.food_list = []

    #加入一个食物到食物列表
    def add(self, xy):
        self.food_list.append(xy)

    #从食物列表移除一个食物
    def remove(self, xy):
        self.food_list.remove(xy)


#############################################################
#类相关的全局变量
snakes = []
food = Food()

#############################################################
#函数定义


#命令行参数处理
def use_terminal_argv():

    sys_arg_count = len(sys.argv)  #获取参数数量
    if sys_arg_count > 1:
        for i in range(1, sys_arg_count):

            sys.argv[i] = sys.argv[i].lower()  #统一参数为小写

            if sys.argv[i].startswith("-max"):  #限定蛇的数量
                splited = sys.argv[i].split("=")
                global snake_limit
                snake_limit = int(splited[1])

            elif sys.argv[i].startswith("-port"):  #是否更换端口号
                splited = sys.argv[i].split("=")
                global server_port
                server_port = int(splited[1])


#判断xy坐标是否与任何蛇冲突
def in_snakes(xy):
    if len(snakes) == 0: return False
    for snake in snakes:
        for body_xy in snake.body:
            if body_xy == xy:
                return True
    return False


#判断xy坐标是否与食物冲突
def colliding_with_food(xy):
    for f_xy in food.food_list:
        if xy == f_xy:
            return True
    return False


#返回一个不与蛇或食物重叠的xy坐标
def find_empty_xy():
    xy = get_random_xy()
    while colliding_with_food(xy) or in_snakes(xy):
        xy = get_random_xy()
    return xy


#随机生成一个xy坐标
def get_random_xy():
    a = random.randint(0, max_x_box - 1)
    x = a * box_size
    b = random.randint(0, max_y_box - 1)
    y = b * box_size
    xy = (x, y)
    return xy


#找一个空的xy坐标，作为蛇的出生点
def find_spawn_point(snakes):
    xy = get_random_xy()
    if len(snakes) == 0:
        return xy
    else:
        for snake in snakes:
            if snake.xy == xy:
                return find_spawn_point(snakes)
            else:
                return xy


#打包地图上所有蛇和食物的坐标
def package_map_data():
    snake_body_list = []
    for snake in snakes:
        snake_body_list.append(snake.body)

    package = "snakes:" + str(snake_body_list) + "food:" + str(food.food_list)
    package = package.replace(" ", "")
    return package


#用于拒绝服务的线程，通知游戏人数已满
def client_deny_thread(server):
    while True:
        client, client_addr = server.accept()
        msg = f"msg(拒绝): 很抱歉呢，游戏人数已满，亲无法再加入。¯\_(ツ)_/¯"
        client.sendall(str.encode(msg))
        client.close()


#定义对应每个客户端的游戏逻辑线程
def client_manager(client, client_id):

    print(f"client thread: {client_id} started")

    #当本客户端的蛇还活着时
    while snakes[client_id].alive:
        #接受客户端按键
        keys = client.recv(256).decode()
        print(f"client: {client_id} sent keys:{keys}")
        # if keys == "quit":
        #     print(f"client {client_id} quit.")
        #     break

        #若网络延迟导致多组信息到达，选择最后一组
        keys = keys.split("|")
        keys.remove("")
        keys = keys[-1]

        #keys由string变为int列表
        # keys = keys.removeprefix("[")
        # keys = keys.removesuffix("]")
        keys = keys[1:]
        keys = keys[:-1]
        keys = keys.split(",")
        temp_keys = []
        for val in keys:
            temp_keys.append(int(val))

        if UP in temp_keys:
            snakes[client_id].change_direction(UP)
        if DOWN in temp_keys:
            snakes[client_id].change_direction(DOWN)
        if LEFT in temp_keys:
            snakes[client_id].change_direction(LEFT)
        if RIGHT in temp_keys:
            snakes[client_id].change_direction(RIGHT)

        #根据按键组更新蛇的信息：
        if 9 in temp_keys or SPACE in temp_keys:
            snakes[client_id].move()

        data = package_map_data()
        print(f"data for client {client_id} sent: {data}")
        client.sendall(str.encode(data))

    snakes[client_id].clear()
    snakes[client_id].alive = False
    print("alived", snakes[client_id].alive)
    data = f"msg(游戏结束): 您的蛇装车了, 您的分数: {snakes[client_id].len}"
    print(f"data made for client {client_id}:", data)
    client.sendall(str.encode(data))

    data = client.recv(256).decode()
    print(data)
    time.sleep(1)
    client.close()
    print(f"exiting client{client_id} thread")


#############################################################
#定义主函数
def main():

    #处理命令行参数
    use_terminal_argv()

    #初始化蛇对象的列表
    for i in range(snake_limit):
        snake = Snake()
        snake.id = i
        snake.xy = find_spawn_point(snakes)  #找到出生点
        snake.body.append(snake.xy)
        snake.len += 1
        snakes.append(snake)

    #创建服务器socket，参数含义（ipv4，TCP）
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    #服务器ip和端口号与server socket绑定
    try:
        server.bind((server_ip, server_port))
    except Exception as e:
        server.close()
        print(e)  #如果绑定失败，关闭socket，退出程序
        quit()

    #开始监听新的连接
    print("等待新玩家加入...")
    server.listen(10)
    clients = []  # 用于储存所有连接到的client socket

    #初始化食物坐标
    for _ in range(snake_limit * 2):
        temp_xy = find_empty_xy()
        food.add(temp_xy)
    print(f"food made： {food.food_list}")

    #游戏匹配阶段-------------------------------------------
    for current_snake in range(snake_limit):

        #接受客户端连接请求
        client, client_addr = server.accept()

        #给被接受的玩家发送编号
        msg = f"msg(同意)：游戏请求被接受，请等待匹配，您的编号为：{current_snake}"
        client.sendall(str.encode(msg))
        print(f"{msg}已发送")
        clients.append(client)

    #启动一个专门拒绝继续向服务器发出连接请求的线程
    deny_thread = threading.Thread(target=client_deny_thread, args=(server, ))
    deny_thread.daemon = True  #设置为守护进程，防止阻止程序正常退出
    deny_thread.start()

    for i in range(snake_limit):
        msg = f"msg(匹配完成)：所有玩家均已匹配完成，请准备游戏"
        clients[i].sendall(str.encode(msg))
        reply = clients[i].recv(256).decode()
        print(reply)  #在服务器命令行展示玩家准备就绪

    #为每一个客户端开启一个专门的处理进程
    for i, client in enumerate(clients):
        client_thread = threading.Thread(target=client_manager,
                                         args=(client, i))
        client_thread.start()

    #等待目前所有的线程结束
    for thread in threading.enumerate():
        if thread != threading.current_thread() and not thread.daemon:
            thread.join()

    print("游戏结束，各个客户端的分数如下：")

    for snake in snakes:
        print(f"客户端{snake.id}的分数为：{snake.len}")

    print("所有线程均已结束运行...")

    server.close()

    print("server socket关闭")
    print("服务器程序关闭")


#############################################################
#主函数入口
if __name__ == "__main__":
    main()
